module dactor.coroutine;

private import dactor.scheduler;
private import dactor.log;
private import core.thread;
private import std.stdio;
private import std.string;

/**
 * Pid is the identity of Coroutine, for thread safe, remote,
 * etc..
 */
struct Pid {
    uint node = 0;
    uint id   = 0;
    uint time = 0;

    static const Pid INVALID_PID;

    public string toString() const {
        return std.string.format("<%d.%d.%d>", node, id, time);
    }
}

class Message {
    Pid from;
    Pid to;
    string toName;
    Object message;
}

/**
 * Coroutine class derived from core.thread.Fiber, added pid,
 * mailbox, monitors, links etc..
 */
class Coroutine : public Fiber {

    public enum Status {
        BEGIN,       // not start
        RUNNING,     // running
        SLEEPING,    // sleeping
        RECEIVING,   // waiting messages
        WAITING,     // waiting events
        SCHEDULING,  // in schedule queue
        DEAD         // dead
    }

    package Coroutine prev;
    package Coroutine next;

    package Status status;    // run status

    package Pid pid;
    package string name;

    private Message[] mailbox; // messages in mailbox
    private Pid[] monitors;   // monitored by other coroutines, not used
    private Pid[] links;      // link to other coroutines, not used

    public this(void delegate() dg, size_t stackSize = 1024) {
        super(dg, stackSize);
    }

    public this(void function() fn, size_t stackSize = 1024) {
        super(fn, stackSize);
    }

    public void addMessage(Message msg) {
        mailbox ~= msg;
    }

    public Message getMessage() {
        if (mailbox.length) {
            Message m = mailbox[0];
            mailbox = mailbox[1 .. $];
            return m;
        }
        return null;
    }

    //////////////////////////////////////////
    public static void yield() {
        // Scheduler.instance.schedule(Scheduler.instance.getCurrent);
        Fiber.yield();
    }
}


/**
 * Params:
 * stackSize = stack size of coroutine
 * fn = procedure for spawn.
 * args = procedure arguments.
 *
 * Returns:
 * Coroutine created.
 *
 * Example:
 * ------------
 * void test(int n) {
 *     writefln("test: %d", n);
 * }
 * Coroutine coroutine = spawn(&test, 1);
 * ------------
 */
Pid spawn(Args...)(size_t stackSize, string name, void function(Args) fn, Args args) {
    void proc() {
        fn(args);
    }
    return doSpawn(stackSize, name, &proc);
}

Pid spawn(Args...)(size_t stackSize, void function(Args) fn, Args args) {
    void proc() {
        fn(args);
    }
    return doSpawn(stackSize, "", &proc);
}

Pid spawn(Args...)(void function(Args) fn, Args args) {
    void proc() {
        fn(args);
    }
    return doSpawn(1024, "", &proc);
}

Pid spawn(Args...)(string name, void function(Args) fn, Args args) {
    void proc() {
        fn(args);
    }
    return spawn(1024, name, &proc);
}



/**
 * Params:
 * stackSize = stack size of coroutine
 * dg = procedure for spawn.
 * args = procedure arguments.
 *
 * Returns:
 * Coroutine created.
 *
 * Example:
 * ------------
 * void test(int n) {
 *     writefln("test: %d", n);
 * }
 * Coroutine coroutine = spawn(&test, 1);
 * ------------
 */


Pid spawn(Args...)(size_t stackSize, string name, void delegate(Args) dg, Args args) {
    void proc() {
        dg(args);
    }
    return doSpawn(stackSize, name, &proc);
}

Pid spawn(Args...)(size_t stackSize, void delegate(Args) dg, Args args) {
    void proc() {
        dg(args);
    }
    return spawn(stackSize, "", &proc);
}

Pid spawn(Args...)(void delegate(Args) dg, Args args) {
    void proc() {
        dg(args);
    }
    return spawn(1024, "", &proc);
}

Pid spawn(Args...)(string name, void delegate(Args) dg, Args args) {
    void proc() {
        dg(args);
    }
    return spawn(1024, name, &proc);
}

private
Pid doSpawn(size_t stackSize, string name, void delegate() proc) {
    Coroutine coroutine = new Coroutine(proc, stackSize);
    if (name.length > 0) {
        if (!Scheduler.instance.registerName(name, coroutine))
            //return Pid.INVALID_PID;
            return Pid();
    }
    Pid pid = Scheduler.instance.register(coroutine);
    Scheduler.instance.schedule(coroutine);
    return pid;
}

/**
 * Returns:
 * Current running coroutine.
 */
Coroutine self() {
    return Scheduler.instance.getCurrent();
}

/**
 * Returns:
 * Current running coroutine's pid.
 */
Pid selfPid() {
    return self().pid;
}

